﻿/****
 * Copyright (c) 2012 Nabil Abdel-Hafeez
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files probuff,
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
****/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace proBuffCompiler
{
    class ProBuffCompiler
    {
        private static List<String> data = new List<string>();
        private static List<String> messages = new List<string>();
        private static List<String> enums = new List<string>();
        private static StreamWriter writer = null;
        private static string beginSwitch = "switch (fieldNumber)\n{\n";
        private static string endSwitch = "default: throw new Exception(\"No valid tag\");\n}\n}\n\n";
        private static string addTabs(int c)
        {
            StringBuilder Tabs = new StringBuilder();
            for (int i = 0; i <= c; i++)
            {
                Tabs.Append("\t");
            }
            return Tabs.ToString();
        }
        private static ulong getTagCode(int tag)
        {
            return (ulong)1 << (tag-1 % 64);
        }
        private static bool isValid(string value, bool isType)
        {
            if (isType)
            {
                if (value.Equals("double")) return true;
                if (value.Equals("float")) return true;
                if (value.Equals("int32")) return true;
                if (value.Equals("int64")) return true;
                if (value.Equals("uint32")) return true;
                if (value.Equals("uint64")) return true;
                if (value.Equals("sint32")) return true;
                if (value.Equals("sint64")) return true;
                if (value.Equals("fixed32")) return true;
                if (value.Equals("fixed64")) return true;
                if (value.Equals("sfixed32")) return true;
                if (value.Equals("sfixed64")) return true;
                if (value.Equals("bool")) return true;
                if (value.Equals("string")) return true;
                if (value.Equals("bytes")) return true;
                if (messages.Contains(value) || enums.Contains(value)) return true;
            }
            else
            {
                Regex expresstion = new Regex("^[a-zA-Z_]+[a-zA-Z0-9_]*$");
                if (expresstion.IsMatch(value)) return true;
            }
            return false;
        }
        private static bool isValidModifier(string p)
        {
            if (p.Equals("required") || p.Equals("optional") || p.Equals("repeated")) return true;
            return false;
        }
        private static string ProtoTypeToCSharpType(string type)
        {
            if (type.Equals("double")) return "Double";
            if (type.Equals("float")) return "Single";
            if (type.Equals("int32")) return "Int32";
            if (type.Equals("int64")) return "Int64";
            if (type.Equals("uint32")) return "UInt32";
            if (type.Equals("uint64")) return "UInt64";
            if (type.Equals("sint32")) return "Int32";
            if (type.Equals("sint64")) return "Int64";
            if (type.Equals("fixed32")) return "UInt32";
            if (type.Equals("fixed64")) return "UInt64";
            if (type.Equals("sfixed32")) return "Int32";
            if (type.Equals("sfixed64")) return "Int64";
            if (type.Equals("bool")) return "Boolean";
            if (type.Equals("string")) return "String";
            if (type.Equals("bytes")) return "String";
            if (messages.Contains(type) || enums.Contains(type)) return type;
            return null;
        }
        private static int getDataFormat(string value)
        {
            if (value.Equals("sfixed64")
                || value.Equals("fixed64")
                || value.Equals("fixed32")
                || value.Equals("sfixed32")
                || value.Equals("double")
                || value.Equals("float")) return 3;
            if (value.Equals("sint32")
                || value.Equals("sint64")) return 1;
            if (value.Equals("int32")
                || value.Equals("int64")
                || value.Equals("bool")) return 2;
            return 0;
        }
        private static int getWireTypeCode(string value)
        {
            if (value.Equals("double")
                || value.Equals("sfixed64")
                || value.Equals("fixed64")) return 1;
            if (value.Equals("float")
                || value.Equals("fixed32")
                || value.Equals("sfixed32")) return 5;
            if (value.Equals("int32")
                || value.Equals("int64")
                || value.Equals("uint32")
                || value.Equals("uint64")
                || value.Equals("sint32")
                || value.Equals("sint64")
                || value.Equals("bool")
                || enums.Contains(value)) return 0;
            if (value.Equals("string")
                || value.Equals("bytes")
                || messages.Contains(value)) return 2;
            throw new InvalidMessageException("No valid type declaration");
        }
        private static string getSwitchFlagPart(bool isPacked, string type, int tag, bool isRepeated = false)
        {
            int flag = 0;
            if (isPacked || messages.Contains(type) || type.Equals("string"))
            {
                flag |= 2;
                if (!type.Equals("string"))
                    flag |= 1 << 4;
            }
            else if (!(getWireTypeCode(type) == 2 & isPacked)) { flag |= getWireTypeCode(type); }
            else throw new InvalidMessageException("WireType two types as " + type + " could not be marked as packed.");
            if (isRepeated) flag |= 1 << 4;
            if (isPacked) flag |= 1 << 3;
            flag |= getDataFormat(type) << 5;
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("case " + tag + ":");
            builder.AppendLine("return " + flag + ";");
            return builder.ToString();
        }
        private static string getSwitchSetPartRepeated(string type, string name, int tag, bool isPacked)
        {
            string ty, uLong = String.Empty;
            if(getDataFormat(type) != 3) uLong = "(ulong)";
            if (isPacked && getDataFormat(type) != 1) uLong = String.Empty;
            if ((ty = ProBuffCompiler.ProtoTypeToCSharpType(type)) == null) throw new InvalidMessageException("No valid type declaration");
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("case " + tag + ":");
            if (!messages.Contains(type) && !enums.Contains(type))
                if (ty.Contains("Int") || ty.StartsWith("D") || ty.StartsWith("Si")) builder.AppendLine("this." + name + ".Add((" + ty + ")" + uLong + "(value));");
                else if (ty.StartsWith("Bo")) builder.AppendLine("this." + name + ".Add(Convert.ToBoolean(" + uLong + "(value)));");
                else builder.AppendLine("this." + name + ".Add((string)value);");
            else if (enums.Contains(type)) builder.AppendLine("this." + name + ".Add((" + ty + ")(ulong)(value));");
            else builder.AppendLine("this." + name + ".Add( value as " + ty + ");");
            builder.AppendLine("tagsThatAreSet" + (int)(tag - 1 >> 6) + " |= 0x" + getTagCode(tag).ToString("X") + ";");
            builder.AppendLine("break;");
            return builder.ToString();
        }

        private static string getSwitchSetPart(string type, string name, int tag)
        {
            string ty, uLong = String.Empty;
            if (getDataFormat(type) != 3) uLong = "(ulong)";
            if ((ty = ProBuffCompiler.ProtoTypeToCSharpType(type)) == null) throw new InvalidMessageException("No valid type declaration");
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("case " + tag + ":");
            if (!messages.Contains(type) && !enums.Contains(type))
                if (ty.Contains("Int") || ty.StartsWith("D") || ty.StartsWith("Si")) builder.AppendLine("this." + name + " = (" + ty + ")" + uLong + "(value);");
                else if (ty.StartsWith("Bo")) builder.AppendLine("this." + name + " = Convert.ToBoolean(" + uLong + "(value));");
                else builder.AppendLine("this." + name + " = (string)(value);");
            else if (enums.Contains(type)) builder.AppendLine("this." + name + " = (" + ty + ") (ulong)(value);");
            else builder.AppendLine("this." + name + " = value as " + ty + ";");
            builder.AppendLine("break;");
            return builder.ToString();
        }
        private static string getSwitchGetPartRepeated(string name, int tag)
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("case " + tag + ":");
            builder.AppendLine("tagsThatAreSet" + (int)(tag - 1 >> 6) + " |= 0x" + getTagCode(tag).ToString("X") + ";");
            builder.AppendLine("return " + name + ";");
            return builder.ToString();
        }
        private static string getSwitchGetPart(string name, string type, int tag)
        {
            string ty, cast = String.Empty;
            if (getWireTypeCode(type) == 0) cast = "(ulong)"; 
            if ((ty = ProBuffCompiler.ProtoTypeToCSharpType(type)) == null) throw new InvalidMessageException("No valid type declaration");
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("case " + tag + ":");
            if (messages.Contains(ty)) builder.AppendLine("return " + name + " ?? new " + ty + "();");
            else if (ty.Equals("String")) builder.AppendLine("return " + name + " ?? String.Empty;");
            else if (ty.Equals("bool")) builder.AppendLine("return " + "Convert.ToUInt64(" + name + ");");
            else builder.AppendLine("return " + cast + name + ";");
            return builder.ToString();
        }
        private static string createProperty(string type, string name, int tag)
        {
            string ty;
            if ((ty = ProBuffCompiler.ProtoTypeToCSharpType(type)) == null) throw new InvalidMessageException("No valid type declaration");
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("private " + ty + " _" + name + ";");
            builder.AppendLine("public bool has" + char.ToUpper(name[0]) + name.Substring(1) + "()\n{");
            builder.AppendLine("return ((tagsThatAreSet" + (int)(tag - 1 >> 6) + " & 0x" + getTagCode(tag).ToString("X") + ") == 0x" + getTagCode(tag).ToString("X") + ");");
            builder.AppendLine("}");
            builder.AppendLine("public " + ty + " " + name);
            builder.AppendLine("{");
            builder.AppendLine("      get { return _" + name + "; }");
            builder.AppendLine("set {");
            builder.AppendLine(" _" + name + " = value;");
            if (!messages.Contains(ty) && !ty.Equals("string")) builder.AppendLine("tagsThatAreSet" + (int)(tag - 1 >> 6) + " |= 0x" + getTagCode(tag).ToString("X") + ";");
            else
            {
                builder.Append("if(value == null) tagsThatAreSet" + (int)(tag - 1 >> 6) + " &= 0x" + (~getTagCode(tag)).ToString("X") + ";");
                builder.Append("else tagsThatAreSet" + (int)(tag - 1 >> 6) + " |= 0x" + getTagCode(tag).ToString("X") + ";");
            }
            builder.AppendLine("}");
            builder.AppendLine("}");
            return builder.ToString();
        }
        private static string createRepeated(string type, string name,int tag)
        {
            string ty;
            if ((ty = ProBuffCompiler.ProtoTypeToCSharpType(type)) == null) throw new InvalidMessageException("No valid type declaration");
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("private readonly global::System.Collections.Generic.List<" + ty + "> _" + name + "= new global::System.Collections.Generic.List<" + ty + ">();");
            builder.AppendLine("public bool has" + char.ToUpper(name[0]) + name.Substring(1) + "(){ return true; }");
            builder.AppendLine("public global::System.Collections.Generic.List<" + ty + "> " + name);
            builder.AppendLine("{");
            builder.AppendLine("get {\n tagsThatAreSet" + (int)(tag - 1 >> 6) + " |= 0x" + getTagCode(tag).ToString("X") + ";");
            builder.AppendLine(" return _" + name + ";\n}");
            builder.AppendLine("}");
            return builder.ToString();
        }
        private static string checkValidCSharpName(string input)
        {
            Regex expresstion = new Regex("^[a-zA-Z_]+[a-zA-Z0-9_]*$");
            if (!expresstion.IsMatch(input)) throw new Exception("The string " + input + " is not valid");//throw new Exception();
            string[] content = null;
            try
            {
                content = File.ReadAllLines(Path.Combine(Environment.CurrentDirectory, "CSharpKeywords.txt"));
            }
            catch (Exception e)
            {
                System.Console.Write(e.Message);
                Process p = new Process();
                p.Kill();
            }

            List<string> invalid = new List<string>(content.Length * 2);
            foreach (String part in content)
            {
                invalid.AddRange(part.Split(new char[] { ' ', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries));
            }
            foreach (String part in invalid)
            {
                if (part.Equals(input)) throw new Exception("The string " + input + " is not valid");
            }
            return input;
        }
        private static void readMessageOrEnum()
        {
            if (data.Count <= 0) return;
            if (data[0].Equals("message"))
            {
                HashSet<int> tags = new HashSet<int>();
                int tag;
                string cName = ProBuffCompiler.checkValidCSharpName(data[1]);
                HashSet<int> tagsReq = new HashSet<int>();
                data.RemoveAt(0);
                writer.WriteLine("class " + Char.ToUpper(cName[0]) + cName.Substring(1) + ":IMessage");
                if (!data[1].Equals("{")) throw new InvalidMessageException("Message declaration for " + cName + " is not Valid.");
                writer.WriteLine("{");
                ;
                data.RemoveAt(0);
                data.RemoveAt(0);
                StringBuilder checkBuilder = new StringBuilder();
                StringBuilder getExistingTagsBuilder = new StringBuilder();
                StringBuilder setSwitchBuilder = new StringBuilder();
                StringBuilder getSwitchBuilder = new StringBuilder();
                StringBuilder flagSwitchBuilder = new StringBuilder();
                checkBuilder.AppendLine("public bool checkRequirements()\n{");
                getExistingTagsBuilder.Append( "public ulong[] getExistingTags()\n{");
                setSwitchBuilder.Append( "public void setByTag(int fieldNumber, object value)\n{");
                getSwitchBuilder.Append( "public object getByTag(int fieldNumber)\n{");
                flagSwitchBuilder.Append( "public int getFlags(int fieldNumber)\n{");
                setSwitchBuilder.Append( ProBuffCompiler.beginSwitch);
                getSwitchBuilder.Append( ProBuffCompiler.beginSwitch);
                flagSwitchBuilder.Append( ProBuffCompiler.beginSwitch);
                if (!data[0].Equals("}"))
                {

                moreParams:
                    while ((data.Count > 0) && isValidModifier(data[0]) && isValid(data[1], true) && isValid(data[2], false))
                    {
                        if ((tag = Int32.Parse(data[4])) <= 0
                            || (tag > 536870911)
                            || (tag == 19000)
                            || (tag == 19999)) throw new InvalidMessageException("Tag numbers " + tag + "is not valid");
                        if (!tags.Add(tag)) throw new MultipleTagException();
                        if (data[0].Equals("repeated"))
                        {
                            bool isPacked = false;
                            if (!data[5].Equals(";")) if (data[5].Equals("[packed=true]")) isPacked = true;
                                else throw new InvalidMessageException("Property declaration in " + cName + " is not Valid.");
                            writer.Write(ProBuffCompiler.createRepeated(data[1], data[2], tag));
                            setSwitchBuilder.Append(ProBuffCompiler.getSwitchSetPartRepeated(data[1], data[2], tag, isPacked));
                            getSwitchBuilder.Append(ProBuffCompiler.getSwitchGetPartRepeated(data[2], tag));
                            flagSwitchBuilder.Append(ProBuffCompiler.getSwitchFlagPart(isPacked, data[1], tag, true));
                        }
                        else
                        {
                            if (data[0].Equals("required")) tagsReq.Add(tag);
                            writer.Write(ProBuffCompiler.createProperty(data[1], data[2], tag));
                            setSwitchBuilder.Append(ProBuffCompiler.getSwitchSetPart(data[1], data[2], tag));
                            getSwitchBuilder.Append(ProBuffCompiler.getSwitchGetPart(data[2], data[1], tag));
                            flagSwitchBuilder.Append(ProBuffCompiler.getSwitchFlagPart(false, data[1], tag));


                        }
                        bool b = true;
                        while (b)
                        {
                            if (data[0].Equals(";")) b = false;
                            data.RemoveAt(0);
                        }
                    }
                    if (data.Count <= 0) return;
                    if (data[0].Equals("}"))
                    {


                    }
                    else if (data[0].Equals("message") || data[0].Equals("enum"))
                    {
                        ProBuffCompiler.readMessageOrEnum();
                        goto moreParams;
                    }
                    else throw new InvalidMessageException("Message declaration for " + cName + " is not Valid.");

                }
                writer.Write(flagSwitchBuilder.ToString());
                writer.Write(ProBuffCompiler.endSwitch);
                writer.Write(setSwitchBuilder.ToString());
                writer.Write(ProBuffCompiler.endSwitch);
                writer.Write(getSwitchBuilder.ToString());
                writer.Write(ProBuffCompiler.endSwitch);

                if (tags.Count > 0)
                {
                    int[] Tags = new int[tags.Count];
                    tags.CopyTo(Tags);
                    Array.Sort(Tags);
                    ulong[] allTags = new ulong[(Tags.Max() - 1 >> 6) + 1];
                    for (int i = 0; i < Tags.LongLength; i++)
                    {
                        allTags[Tags[i] - 1 >> 6] |= getTagCode(Tags[i]);
                    }
                    getExistingTagsBuilder.AppendLine("ulong[] result = new ulong[" + allTags.LongLength + "];");
                    for (int i = 0; i < allTags.LongLength; i++)
                    {
                        getExistingTagsBuilder.Append("\n result[" + i + "] = (tagsThatAreSet" + i + " & " + allTags[i] + ");");
                    }
                    getExistingTagsBuilder.AppendLine("\n return result;\n" +  "}");
                    ;
                }
                else getExistingTagsBuilder.AppendLine("\n return null;\n" +  "}");
                ;

                writer.WriteLine("public int getTagCount(){ return " + tags.Count + "; }");
                writer.WriteLine(getExistingTagsBuilder.ToString());

                if (tagsReq.Count > 0)
                {
                    int[] reqTags = new int[tagsReq.Count];
                    tagsReq.CopyTo(reqTags);
                    Array.Sort(reqTags);
                    ulong[] reqValues = new ulong[(reqTags.Max() - 1 >> 6) + 1];
                    for (int i = 0; i < reqTags.LongLength; i++)
                    {
                        reqValues[reqTags[i] - 1 >> 6] |= getTagCode(reqTags[i]);
                    }
                    checkBuilder.AppendLine("return\n( ((tagsThatAreSet0 & " + reqValues[0] + ") == " + reqValues[0] + ") ");
                    for (int i = 1; i < reqValues.LongLength; i++)
                    {
                        checkBuilder.Append("\n&& ((tagsThatAreSet" + i + " & " + reqValues[i] + ") == " + reqValues[i] + ") ");
                    }
                    checkBuilder.AppendLine(");");
                }
                else checkBuilder.AppendLine("return true;");
                checkBuilder.AppendLine("\n" +  "}");
                ;
                writer.WriteLine(checkBuilder.ToString());
                if (tags.Count > 0)
                for (int i = 0; i < (tags.Max() - 1 >> 6) + 1; i++)
                {
                    writer.WriteLine("private ulong tagsThatAreSet" + i + " = 0 ;");
                }
                data.RemoveAt(0);
                writer.WriteLine("}");
                ;
            }
            if (data.Count <= 0) return;
            if (data[0].Equals("enum"))
            {
                string eName = ProBuffCompiler.checkValidCSharpName(data[1]);
                HashSet<int> values = new HashSet<int>();
                int value;
                data.RemoveAt(0);
                writer.WriteLine("enum " + eName);
                if (!data[1].Equals("{")) throw new InvalidMessageException("Enum declaration for " + eName + " is not Valid.");
                writer.WriteLine("{");
                ;
                data.RemoveAt(0);
                data.RemoveAt(0);
                while (isValid(data[0], false) && data[1].Equals("=") && values.Add(value = Int32.Parse(data[2])) && data[3].Equals(";"))
                {
                    writer.WriteLine(data[0] + " = " + value + ",");
                    data.RemoveAt(0);
                    data.RemoveAt(0);
                    data.RemoveAt(0);
                    data.RemoveAt(0);
                }
                if (!data[0].Equals("}")) throw new InvalidMessageException("Enum declaration for " + eName + " is not Valid.");
                data.RemoveAt(0);
                writer.WriteLine("}");
                ;
            }
            writer.Flush();
        }
        
        static void Main(string[] args)
        {
            try
            {
                string pathIn;
                string namesp;
                string pathOut;
                if (args.Length == 2)
                {
                    pathIn = Path.GetFullPath(args[0]);
                    namesp = Path.GetFileNameWithoutExtension(args[1]);
                    pathOut = Path.GetFullPath(args[1]);
                }
                else if (args.Length == 3)
                {
                    pathIn = Path.GetFullPath(args[0]);
                    namesp = args[2];
                    pathOut = Path.GetFullPath(args[1]);
                }
                else
                {
                    System.Console.WriteLine(@"Usage is {InPath\File.proto} {OutPath\OutPutFile} and optional {NameSpace}");
                    return;
                }
                if (!Path.GetExtension(args[0]).ToLower().Equals(".proto"))
                {
                    System.Console.WriteLine(@"Usage is {InPath\File.proto} {OutPath\OutPutFile} and optional {NameSpace}");
                    return;
                }
                ProBuffCompiler.writer = new StreamWriter(pathOut);
                string[] license = File.ReadAllLines(Directory.GetCurrentDirectory() + @"\License.txt");
                for (int i = 0; i < license.Length; i++)
                {
                    writer.WriteLine(license[i]);
                }
                string[] content = File.ReadAllLines(pathIn);
                int notNeededLineCount = 0;
                string trimed, c;
                for (int i = content.Length - 1; i >= 0; i--)
                {
                    c = content[i];
                    trimed = c.Trim();
                    if (trimed.StartsWith("//") || trimed == string.Empty)
                    {
                        content[i] = null;
                        notNeededLineCount++;
                        continue;
                    }
                    else if (c.Contains("//"))
                    {
                        trimed = c = content[i] = c.Split(new string[] { "//" }, StringSplitOptions.RemoveEmptyEntries)[0];
                    }
                    if (c.Contains('=')) trimed = c = content[i] = c.Replace("=", " = ");
                    if (c.Contains('{')) c = trimed = content[i] = c.Replace("{", " { ");
                    if (c.Contains('}')) c = trimed = content[i] = c.Replace("}", " } ");
                    if (c.Contains(';')) c = trimed = content[i] = c.Replace(";", " ; ");
                    if (c.Contains('['))
                    {
                        string substring = c;
                        StringBuilder build = new StringBuilder();
                        while (substring.Contains('['))
                        {
                            build.Append(substring.Substring(0, substring.IndexOf('[')));
                            build.Append(" " + substring.Substring(substring.IndexOf('['), substring.IndexOf(']') - substring.IndexOf('[') + 1).Replace(" ", string.Empty) + " ");
                            substring = (substring.IndexOf(']') + 1 < substring.Length) ? substring.Substring(substring.IndexOf(']') + 1) : string.Empty;
                        }
                        build.Append(substring);
                        c = trimed = content[i] = build.ToString().Trim();
                    }
                    content[i] = content[i].Trim();
                }
                ProBuffCompiler.data = new List<string>((content.Length - notNeededLineCount) << 2);
                string[] parts;
                foreach (string part in content)
                {
                    if (part != null)
                    {
                        parts = part.Split(new char[] { ' ', '\r', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                        ProBuffCompiler.data.AddRange(parts);
                        if (part.Contains("message") || part.Contains("enum"))
                        {
                            for (int i = 1; i < parts.Length; i++)
                            {
                                if (parts[i - 1].Equals("message")) messages.Add(parts[i]);
                                else if (parts[i - 1].Equals("enum")) enums.Add(parts[i]);
                            }
                        }
                    }
                }
                writer.WriteLine("using System;");
                writer.WriteLine("using ProBuff;\n\n");
                writer.WriteLine("namespace " + namesp + "\n{");
                while (data.Count > 0)
                {
                    readMessageOrEnum();
                }
                writer.WriteLine("}");
                writer.Flush();
            }
            catch (MultipleTagException e)
            {
                System.Console.WriteLine(e.ToString());
                System.Console.WriteLine(e.Message);
                return;
            }
            catch (InvalidMessageException e)
            {
                System.Console.WriteLine(e.ToString());
                System.Console.WriteLine(e.Message);
                return;
            }
            catch (Exception e)
            {
                System.Console.WriteLine(e.ToString());
                System.Console.WriteLine(e.Message);
                System.Console.WriteLine(e.StackTrace);
                return;
            }
        }
    }
}
